package com.innovatio.validator;

import com.innovatio.validator.annotations.Validation;
import com.innovatio.validator.exceptions.ValidatorException;
import com.innovatio.validator.handlers.ValidationHandler;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.util.ResourceBundle;

/**
 * This code is property of Innovatio Software Solutions, Inc.
 * User: Ramon
 * Refactored by: Edxe
 * Date: Mar 4, 2009
 * Time: 1:36:09 PM
 */
public final class BeanValidator {

    private Validable validableObj;
    private boolean isValidBean;
    private ValidationErrors validationErrors;
    private ResourceBundle bundle;

    private BeanValidator(Validable v, ResourceBundle bundle) {
        this.validableObj = v;
        this.bundle = bundle;
    }

    /**
     * rturns an instance of the  BeanValidator class
     *
     * @param v the object which is an instance of Validable to be validated
     */
    public static BeanValidator newInstance(Validable v) {
        return newInstance(v, null);
    }

    public static BeanValidator newInstance(Validable v, ResourceBundle bundle) {
        return new BeanValidator(v, bundle);
    }

    /**
     * create a new instance of the ValidationBean class and validate the submitter bean.
     *
     * @param v the object which is an instance of Validable to be validated
     */
    public static BeanValidator newInstanceAndValidate(Validable v) throws ValidatorException {
        return newInstanceAndValidate(v, null);
    }

    /**
     * create a new instance of the ValidationBean class and validate the submitter bean. also binds
     * the boundle keys to the messages that will be returned with errors if there's any.
     */
    public static BeanValidator newInstanceAndValidate(Validable v, ResourceBundle bundle) throws ValidatorException {
        BeanValidator bv = newInstance(v, bundle);
        bv.validate();
        return bv;
    }

    /**
     * return if the bean is valid. If the validate method has not being invoke it will return false
     */
    public boolean isValidBean() {
        return isValidBean;
    }


    /**
     * return if any the validation errors for the validate bean, if the validate method has not beign invoke
     * the return value will be null
     */
    public ValidationErrors getValidationErrors() {
        return validationErrors;
    }

    /**
     * commit the process of validating the bean.
     */
    public ValidationErrors validate() throws ValidatorException {
        ValidationErrors ve = new ValidationErrors();
        try {
            for (Field field : validableObj.getClass().getDeclaredFields()) {
                String fieldName = field.getName();
                String value = BeanUtils.getSimpleProperty(validableObj, fieldName);
                ve.appendErros(validate(validableObj, field, fieldName, value));
            }

            this.validationErrors = ve;
            this.isValidBean = !ve.hasErrors();
        } catch (Exception e) {
            throw new ValidatorException(e);
        }

        return ve;
    }


    /**
     * Finds all Annotations that have Validate annotations themselves,
     * then coordinating validation
     */
    private ValidationErrors validate(Validable container, AnnotatedElement element, String fieldName, String value) throws ValidatorException {
        Annotation[] annotations = element.getAnnotations();
        ValidationErrors ve = new ValidationErrors();

        for (Annotation annotation : annotations) {
            Validation validate = annotation.annotationType().getAnnotation(Validation.class);

            if (validate != null) {
                try {
                    ValidationHandler validateHandler = validate.value().newInstance();
                    ValidationErrors.Error error = validateHandler.validate(annotation, container, fieldName, value);

                    if (error != null) {
                        if (this.bundle != null) {
                            error.setErrorMessage(bundle.getString(error.getErrorMessage()));
                        }
                        ve.addValidationError(fieldName, error);
                    }
                } catch (ValidatorException vex) {
                    throw vex;
                } catch (Exception e) {
                    throw new ValidatorException(e);
                }
            }

        }
        return ve;
    }


}
